Skip to content

feat: replace sidebar NSOutlineView with SwiftUI List#234

Merged
2ndalpha merged 4 commits intomasterfrom
feat/swiftui-sidebar
Feb 28, 2026
Merged

feat: replace sidebar NSOutlineView with SwiftUI List#234
2ndalpha merged 4 commits intomasterfrom
feat/swiftui-sidebar

Conversation

@2ndalpha
Copy link
Owner

Summary

Replace the Editor.xib sidebar (NSOutlineView + Cell + ListController) with a pure SwiftUI List, following the same pattern established in PR #227 for URLSheet. This is PR 1 of 3 in the incremental Editor.xib modernization:

  1. This PR — SwiftUI sidebar (observable model + row view + list view + installer)
  2. PR 2 — SwiftUI content area (text editor + predicate picker + toolbar + status bar)
  3. PR 3 — Eliminate Editor.xib entirely

New Swift files

  • HostsDataStoreObservableObject singleton that bridges ObjC NSNotificationCenter events to @Published properties for SwiftUI reactivity. Mirrors the PCH notification name macros as NSNotification.Name extensions.
  • HostsRowView — SF Symbol-based row view replacing the 400-line custom Cell.m drawing code. Shows active/unsaved/error/syncing/offline badges with accessibility labels.
  • SidebarView — SwiftUI List(selection:) with sections per HostsGroup, context menus (save, activate, show in Finder, open in browser, move, rename, remove), inline rename with validation, and drag-and-drop via SidebarDropDelegate.
  • SidebarInstaller@objc bridge called from EditorController.m to swap the XIB's left pane with an NSHostingView at runtime.

ObjC changes

  • HostsMainController — Added selectHosts: method for programmatic selection from Swift.
  • ListController — Added deactivate method that removes all notification observers, preventing duplicate handling when SwiftUI takes over.
  • EditorController — Calls [SidebarInstaller installIn:splitView] at end of awakeFromNib.
  • Bridging header — Added imports for Node, Hosts, HostsGroup, RemoteHosts, CombinedHosts, Preferences, Error, LocalHostsController, CombinedHostsController, ListController.

Tests

  • HostsDataStoreTests — Notification name constants match ObjC defines, singleton identity, rename/select notification handling.

Test plan

  • Build succeeds (xcodebuild build)
  • All tests pass (xcodebuild test)
  • Manual: Launch app, verify sidebar shows all hosts groups and files
  • Manual: Select a hosts file — editor pane updates
  • Manual: Right-click context menu actions work (save, activate, rename, remove)
  • Manual: Drag a URL onto a group to create a remote hosts file
  • Manual: Drag a hosts file between groups
  • Manual: Inline rename with slash rejection and duplicate name detection

Replace the Editor.xib sidebar (NSOutlineView + Cell + ListController)
with a SwiftUI List, following the same pattern established in PR #227
for URLSheet.

New files:
- HostsDataStore: ObservableObject singleton bridging ObjC notifications
- HostsRowView: SF Symbol-based row replacing 400-line custom Cell drawing
- SidebarView: SwiftUI List with drag-and-drop, context menus, inline rename
- SidebarInstaller: @objc bridge that swaps the XIB pane at runtime

ObjC changes:
- HostsMainController: add selectHosts: for programmatic selection
- ListController: add deactivate to remove duplicate notification observers
- EditorController: call SidebarInstaller from awakeFromNib
- Add confirmation dialog before removing hosts files
- Move .onDrop to per-Section for group-aware drops (files land in the
  correct group instead of always the first one)
- Handle multi-file drops (process all URL providers, not just first)
- Remove .utf8PlainText from drop types (was accepted but never handled)
- Show empty group sections so users can drop into them
- Replace deprecated onCommit with .onSubmit
- Suppress unused result warning on loadObject
Resolve conflicts in project.pbxproj and bridging header by
keeping entries from both branches.
- Combine duplicate .allHostsFilesLoadedFromDisk observer into one
- Remove unreachable deinit from singleton HostsDataStore
- Fix row refresh: reassign hostsGroups instead of objectWillChange.send()
- Change removeHostsFile to moveToTrash:true for user recovery
- Add comment explaining dispatch_async in selectHosts:
@2ndalpha 2ndalpha merged commit b232236 into master Feb 28, 2026
18 checks passed
@2ndalpha 2ndalpha deleted the feat/swiftui-sidebar branch February 28, 2026 15:49
2ndalpha added a commit that referenced this pull request Mar 1, 2026
…237)

Replace the 515-line Editor.xib and ~28 legacy ObjC files (~2800 LOC)
with a programmatic NSWindow containing a single SwiftUI EditorView
root. This is the final step (PR 3 of 3) in the editor modernization
series, following the sidebar (#234) and content area (#236) SwiftUI
migrations.

New SwiftUI components:
- EditorView: root view with NavigationSplitView, owns @StateObject
  HostsDataStore
- EditorToolbar: 4 toolbar items using SF Symbols (plus, minus,
  square.and.arrow.down, power)
- StatusBarView: files count label, read-only lock icon, busy spinner
- EditorWindowPresenter: @objc bridge creating NSWindow programmatically

Key changes:
- HostsDataStore converted from singleton to @StateObject-owned instance
- Busy indicator moved from ApplicationController to HostsDataStore with
  thread-safe notification observers (queue: .main)
- Toolbar Remove now uses moveToTrash:true (reversible) matching sidebar
- Window lifecycle (open/close/reopen, dock show/hide) preserved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant